python 缓存函数的设计
Python 的 decorator是一个非常一颗赛艇的语法糖,提供了很多便利。我们可以用其实现很多牛逼的功能,并且对业务代码的侵占性很小。最近业务上遇到了一个缓存的问题,如果之前请求过该函数,则返回缓存的结果就行。代码如下
def add(a,b):
return a+b
如果add(1,2)没有调用过,则调用一次,并缓存该函数的返回结果。如果add(1,2)调用过,则无需再调用add函数,直接返回相应参数的结果就可以。好处在于,如果add是一个很耗费时间的操作,并且属于纯函数的话,则通过缓存,可以大大加快请求的速度(得先命中缓存)。
在python中, 缓存函数的设计可以通过装饰器去实现。优点在于对被缓存的函数影响比较小,甚至无影响。基本无需改动被缓存函数的代码,即可实现缓存的功能。
根据这点,我们可以写一个简单的函数来验证一下我们的思路
local_cache = {}
def cache(func):
def inner_func(*args, **kargs):
if args in local_cache:
result = local_cache.get(args)
else:
result = func(*args, **kargs)
local_cache[args] = result
return result
return inner_func
测试代码
@cache
def add(a, b):
print("func was called")
return a+b
print(add(1+2))
print(add(1+2))
"""
output:
func was called
3
3
"""
根据结果可以看出,add函数只调用一次。结果符合预期。但是这个函数是有问题的,如果我们通过add(a=1 ,b=2)的方式去调用,则无法命中缓存,问题在于上面调用的方式属于kargs,但是我们单纯的只判断args是否在cache中。如果我们同时判断args和kargs是否在cache中,则代码又不pythonic了。
if args in local_cache or str(kargs) in local_cache:
do something
else :
result = func(*args,**kargs)
local_cache[args] = local_cache[str(kargs)] = result
这种情况,我们可以自己根据args和kargs去构造一个字典,并作为缓存的key,类似于kargs。kargs的特点是,key为函数的绑定参数名,value为传入的参数。那么问题来了,怎么获取函数的参数名呢?这里推荐使用python的inspect模块,可以得到好多类型。对于函数来讲,只需要调用getfullargspec(func)``即可获取到函数的各类信息,
add`函数为例,可以得到如下的信息
FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
args则是函数的参数名,现在我们可以根据此,重构我们的代码
def inner_func(*args, **kargs):
signature = {**dict(zip(getfullargspec(func).args, args)), **kargs}
if str(signature) in local_cache:
result = local_cache.get(str(signature))
else:
result = func(*args, **kargs)
local_cache.update({str(signature): result})
return result
return inner_func
在这里我们实现了一个缓存函数的设计,大家可以根据自己的需要,增加其他功能,例如将缓存结果保存,以便下次程序重启后继续使用。
referer
作者:烤土豆啦
链接:https://www.jianshu.com/p/fa937690b4f4
來源:简书